package cc.shacocloud.kotlin.tools.csv

import cc.shacocloud.kotlin.tools.uitls.ComputeIter
import java.io.Closeable
import java.io.Reader
import java.io.Serializable
import java.util.*

/**
 * CSV行解析器
 * @author 思追(shaco)
 */
class CsvParser(

    private val reader: Reader,

    // csv 读取配置
    private val config: CsvReadConfig = CsvReadConfig.DEFAULT
) : ComputeIter<CsvRow>(), Closeable, Serializable {

    companion object {

        const val DEFAULT_ROW_CAPACITY: Int = 10

        /**
         * 字符常量：回车符 `'\r'`
         */
        const val CR: Char = '\r'

        /**
         * 字符常量：换行符 `'\n'`
         */
        const val LF: Char = '\n'

    }

    private val buf = Buffer(2 shl 14)

    /**
     * 前一个特殊分界字符
     */
    private var preChar: Char? = null

    /**
     * 是否在引号包装内
     */
    private var inQuotes = false

    /**
     * 标题行
     */
    private var header: CsvRow? = null

    /**
     * 当前行号
     */
    private var lineNo: Long = -1

    /**
     * 引号内的行数
     */
    private var inQuotesLineCount: Long = 0

    /**
     * 第一行字段数，用于检查每行字段数是否一致
     */
    private var firstLineFieldCount = -1

    /**
     * 最大字段数量，用于初始化行，减少扩容
     */
    private var maxFieldCount = 0

    /**
     * 是否读取结束
     */
    private var finished = false

    override fun computeNext(): CsvRow? {
        return nextRow()
    }

    /**
     * 获取头部字段列表，如果headerLineNo &lt; 0，抛出异常
     *
     * @return 头部列表
     * @throws IllegalStateException 如果不解析头部或者没有调用nextRow()方法
     */
    fun getHeader(): List<String> {
        check(config.headerLineNo >= 0) { "没有可用的标头 - 标头分析已禁用" }
        check(lineNo >= config.beginLineNo) { "没有可用的标头 - 先调用 nextRow（）" }
        return header?.rawFields ?: emptyList()
    }

    /**
     * 读取下一行数据
     *
     * @return CsvRow
     */
    fun nextRow(): CsvRow? {
        var currentFields: List<String>
        var fieldCount: Int

        while (!finished) {
            currentFields = readLine()
            fieldCount = currentFields.size
            if (fieldCount < 1) {
                // 空List表示读取结束
                break
            }

            // 读取范围校验
            if (lineNo < config.beginLineNo) {
                // 未达到读取起始行，继续
                continue
            }
            if (lineNo > config.endLineNo) {
                // 超出结束行，读取结束
                break
            }

            // 跳过空行
            if (config.skipEmptyRows && fieldCount == 1 && currentFields[0].isEmpty()) {
                // [""]表示空行
                continue
            }

            // 检查每行的字段数是否一致
            if (config.errorOnDifferentFieldCount) {
                if (firstLineFieldCount < 0) {
                    firstLineFieldCount = fieldCount
                } else if (fieldCount != firstLineFieldCount) {
                    throw RuntimeException("第 $lineNo 行有 $fieldCount 字段，但第一行有 $firstLineFieldCount 字段")
                }
            }

            // 记录最大字段数
            if (fieldCount > maxFieldCount) {
                maxFieldCount = fieldCount
            }

            //初始化标题
            if (lineNo == config.headerLineNo && null == header) {
                initHeader(currentFields)
                // 作为标题行后，此行跳过，下一行做为第一行
                continue
            }

            return CsvRow(lineNo, header?.headerMap ?: emptyMap(), currentFields)
        }

        return null
    }

    /**
     * 当前行做为标题行
     *
     * @param currentFields 当前行字段列表
     */
    private fun initHeader(currentFields: List<String>) {
        val localHeaderMap = LinkedHashMap<String, Int>(currentFields.size)
        for (i in currentFields.indices) {
            var field = currentFields[i]
            if (config.headerAlias.isNotEmpty()) {
                // 自定义别名
                field = config.headerAlias.get(field) ?: field
            }
            if (field.isNotEmpty() && !localHeaderMap.containsKey(field)) {
                localHeaderMap[field] = i
            }
        }

        header = CsvRow(
            this.lineNo,
            localHeaderMap,
            currentFields
        )
    }

    /**
     * 读取一行数据，如果读取结束，返回size为0的List
     * 空行是size为1的List，唯一元素是 ""
     * 行号要考虑注释行和引号包装的内容中的换行
     *
     * @return 一行数据
     */
    private fun readLine(): List<String> {
        // 矫正行号
        // 当一行内容包含多行数据时，记录首行行号，但是读取下一行时，需要把多行内容的行数加上
        if (inQuotesLineCount > 0) {
            this.lineNo += this.inQuotesLineCount
            this.inQuotesLineCount = 0
        }

        val currentFields: MutableList<String> =
            ArrayList(if (maxFieldCount > 0) maxFieldCount else DEFAULT_ROW_CAPACITY)

        var currentField = StringBuilder(512)
        val buf = this.buf
        //前一个特殊分界字符
        var preChar = this.preChar
        //拷贝长度
        var copyLen = 0

        var inComment = false

        while (true) {
            if (!buf.hasRemaining()) {
                // 此Buffer读取结束，开始读取下一段
                if (copyLen > 0) {
                    buf.appendTo(currentField, copyLen)
                    // 此处无需mark，read方法会重置mark
                }
                if (buf.read(this.reader) < 0) {
                    // CSV读取结束
                    finished = true

                    if (currentField.isNotEmpty() || preChar == config.fieldSeparator) {
                        //剩余部分作为一个字段
                        addField(currentFields, currentField.toString())
                    }
                    break
                }

                //重置
                copyLen = 0
            }

            val c = buf.get()

            // 注释行标记
            if (preChar == null || preChar == CR || preChar == LF) {
                // 判断行首字符为指定注释字符的注释开始，直到遇到换行符
                // 行首分两种，1是preChar < 0表示文本开始，2是换行符后紧跟就是下一行的开始
                if (null != config.commentCharacter && c == config.commentCharacter) {
                    inComment = true
                }
            }
            // 注释行处理
            if (inComment) {
                if (c == CR || c == LF) {
                    // 注释行以换行符为结尾
                    lineNo++
                    inComment = false
                }
                // 跳过注释行中的任何字符
                buf.mark()
                preChar = c
                continue
            }

            if (inQuotes) {
                //引号内，作为内容，直到引号结束
                if (c == config.textDelimiter) {
                    // End of quoted text
                    inQuotes = false
                } else {
                    // 字段内容中新行
                    if (isLineEnd(c, preChar)) {
                        inQuotesLineCount++
                    }
                }
                // 普通字段字符
                copyLen++
            } else {
                // 非引号内
                if (c == config.fieldSeparator) {
                    //一个字段结束
                    if (copyLen > 0) {
                        buf.appendTo(currentField, copyLen)
                        copyLen = 0
                    }
                    buf.mark()
                    addField(currentFields, currentField.toString())
                    currentField = StringBuilder()
                } else if (c == config.textDelimiter) {
                    // 引号开始
                    inQuotes = true
                    copyLen++
                } else if (c == CR) {
                    // \r，直接结束
                    if (copyLen > 0) {
                        buf.appendTo(currentField, copyLen)
                    }
                    buf.mark()
                    addField(currentFields, currentField.toString())
                    preChar = c
                    break
                } else if (c == LF) {
                    // \n
                    if (preChar != CR) {
                        if (copyLen > 0) {
                            buf.appendTo(currentField, copyLen)
                        }
                        buf.mark()
                        addField(currentFields, currentField.toString())
                        preChar = c
                        break
                    }
                    // 前一个字符是\r，已经处理过这个字段了，此处直接跳过
                    buf.mark()
                } else {
                    // 普通字符
                    copyLen++
                }
            }

            preChar = c
        }

        // 还原字段
        this.preChar = preChar

        lineNo++
        return currentFields
    }

    override fun close() {
        super.finish()
        reader.close()
    }

    /**
     * 将字段加入字段列表并自动去包装和去转义
     *
     * @param currentFields 当前的字段列表（即为行）
     * @param field         字段
     */
    private fun addField(currentFields: MutableList<String>, field: String) {
        val textDelimiter = config.textDelimiter.toString()

        // 忽略多余引号后的换行符
        var fieldStr = field.trim()
        fieldStr = fieldStr.removeSurrounding(textDelimiter)
        fieldStr = fieldStr.replace(textDelimiter + textDelimiter, textDelimiter)

        // 忽略字段前后的换行符
        if (config.trimField) {
            fieldStr = fieldStr.trim()
        }

        currentFields.add(fieldStr)
    }

    /**
     * 是否行结束符
     *
     * @param c       符号
     * @param preChar 前一个字符
     * @return 是否结束
     */
    private fun isLineEnd(c: Char, preChar: Char?): Boolean {
        return (c == CR || c == LF) && preChar != CR
    }

    /**
     * 内部 Buffer
     */
    private class Buffer(capacity: Int) : Serializable {

        val buf: CharArray = CharArray(capacity)

        /**
         * 标记位置，用于读数据
         */
        private var mark = 0

        /**
         * 当前位置
         */
        private var position = 0

        /**
         * 读取的数据长度，一般小于 [buf.size]，-1表示无数据
         */
        private var limit = 0

        /**
         * 是否还有未读数据
         *
         * @return 是否还有未读数据
         */
        fun hasRemaining(): Boolean {
            return position < limit
        }

        /**
         * 读取到缓存
         * 全量读取，会重置Buffer中所有数据
         *
         * @param reader [Reader]
         */
        fun read(reader: Reader): Int {
            val length = reader.read(this.buf)
            this.mark = 0
            this.position = 0
            this.limit = length
            return length
        }

        /**
         * 先获取当前字符，再将当前位置后移一位
         * 此方法不检查是否到了数组末尾，请自行使用[hasRemaining]判断。
         *
         * @return 当前位置字符
         * @see hasRemaining
         */
        fun get(): Char {
            return buf[position++]
        }

        /**
         * 标记位置记为下次读取位置
         */
        fun mark() {
            this.mark = this.position
        }

        /**
         * 将数据追加到[StringBuilder]，追加结束后需手动调用[mark] 重置读取位置
         *
         * @param builder [StringBuilder]
         * @param length  追加的长度
         * @see mark
         */
        fun appendTo(builder: StringBuilder, length: Int) {
            builder.appendRange(this.buf, this.mark, this.mark + length)
        }

    }

}
